Server Actions
概要
Form からサーバの非同期関数を直接呼び出せる React の新しい機能 中間コード(API Client)が無くなる
Browser 向けに バンドル されていた API Client が少なくなる 従来は onSubmit イベントハンドラを利用していたため、Hydration が完了するまでユーザはフォーム送信ができなかった
Server Actions では <form> の action 属性を利用することで、Hydration が完了する前にフォーム送信が可能に
use server ディレクティブ
Server Action を利用するには、use server ディレクティブを宣言する必要がある
warning.icon use server ディレクティブは、サーバーサイドのファイルでのみ有効
宣言場所は 2 通り
1. 非同期関数スコープの先頭
code:actions.ts
"use server"
export default function ServerComponent() {
async function myAction(formData: FormData) {
"use server";
// ...
}
}
2. Server Action 専用ファイルを作成する
名前は自由
code:actions.ts
"use server";
export async funtion myAction(formData: FormData) {
// ...
}
code:tsx
"use client";
import { myAction } from './actions';
export default function ClientComponent({ id }: { id: string }) {
return (
<form action={myAction}>
<input type="hidden" name="id" value={id} />
<button type="submit">Add to Cart</button>
</form>
)
}
このオブジェクトからフォームで入力された値を参照できる
code:ts
export async funtion myAction(formData: FormData) {
const id = formData.get("id");
if (typeof id !== "string") {
throw new Error("Validation error");
}
// ...
}
引数のバインド
hidden フィールドを使用して <form> アクションにデータを渡す代わりに、bind メソッドを呼び出して追加の引数を渡すこともできます。
bind メソッドを用いることで、いくつかの引数がすでにバインドされた新しい Server Actiosn を作成可能
code:tsx
"use client";
import { myAction } from "./actions";
export default function ClientComponent({ id }: { id: string }) {
cosnt action = myAction.bind(null, id);
return (
<form action={myAction}>
<input type="hidden" name="id" value={id} />
<button type="submit">Add to Cart</button>
</form>
)
}
これにより、Server Action 関数はバインドされた引数の後ろに FormData を取るようになる
code:ts
export async funtion myAction(id: string, formData: FormData) {
// ...
}
Hydration が完了する前のフォーム送信: Client バリデーションを実行しない Hydration が完了した後のフォーム送信: Client バリデーションを実行しない これを実現するには、<form> の action と onSubmit を切り分ければ良い
実装例
code:tsx
export function PhotoForm({ photo, categories }: Props) {
updatePhoto,
initialFormState({ ...photo })
);
formState.error?.fieldErrors
);
const errors = clientErrors || formState.error?.fieldErrors;
function handleSubmit(event: FormEvent<HTMLFormElement>) {
try {
const formData = new FormData(event.currentTarget);
validateFormData(formData);
setClientErrors(undefined);
} catch (err) {
// Client バリデーションチェックに失敗した場合、preventDefault を呼び出して、action 実行を中断する
event.preventDefault();
if (!(err instanceof ZodError)) throw err;
setClientErrors(transformFiledErrors(err));
}
}
return (
<form action={formDispatch} onSubmit={handleSubmit}>
{/* ... */}
</form>
);
}